#region Usings

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web;
using Karma.Framework.Core.Domain.Tools.Import.Configuration.Fluent;
using Karma.Framework.Core.IoC;
using log4net;
using Karma.Framework.Core.Domain.Tools.Import.Data;
using Karma.Framework.Core.Domain.Tools.Import.Layout;
using Karma.Framework.Core.Domain.Tools.Import.Report;
using Karma.Framework.Core.Domain.Tools.Import.Validation;
using ImporterConfiguration=Karma.Framework.Core.Domain.Tools.Import.Configuration.ImporterConfiguration;

#endregion

namespace Karma.Framework.Core.Domain.Tools.Import
{
    public class DefaultImporterService : IImporterService
    {
        private static readonly ILog LOG = LogManager.GetLogger(typeof (DefaultImporterService).Name);
        private static IDictionary<string, MethodInfo> _entityBuilderMethods = new Dictionary<string, MethodInfo>();
        private static IDictionary<string, object> _entityBuilders = new Dictionary<string, object>();
        private static IDictionary<string, MethodInfo> _persisterMethods = new Dictionary<string, MethodInfo>();
        private static IDictionary<string, MethodInfo> _referenceValidatorMethods = new Dictionary<string, MethodInfo>();
        private static IDictionary<string, MethodInfo> _ukMethods = new Dictionary<string, MethodInfo>();

        public DefaultImporterService(ILayoutFactory layoutFactory, ILayoutFileBuilder layoutFileBuilder,
                                      ILayoutFileReader layoutFileReader, ILayoutValidator layoutValidator,
                                      IDataValidator dataValidator,
                                      IReferenceValidator referenceValidator,
                                      IUniqueKeyValidator uniqueKeyValidator, 
                                        IEntityPersister persister)
        {
            LayoutFactory = layoutFactory;
            FileBuilder = layoutFileBuilder;
            FileReader = layoutFileReader;
            LayoutValidator = layoutValidator;
            DataValidator = dataValidator;
            ReferenceValidator = referenceValidator;
            UniqueKeyValidator = uniqueKeyValidator;
            Persister = persister;
        }

        private ILayoutFactory LayoutFactory { get; set; }
        private ILayoutFileBuilder FileBuilder { get; set; }
        private ILayoutFileReader FileReader { get; set; }
        private ILayoutValidator LayoutValidator { get; set; }
        private IDataValidator DataValidator { get; set; }
        private IReferenceValidator ReferenceValidator { get; set; }
        private IUniqueKeyValidator UniqueKeyValidator { get; set; }
        private IEntityPersister Persister { get; set; }

        #region IImporterService Members

        public EntityDescription[] GetEntities()
        {
            return ImporterConfiguration.ImportableEntities.Select(
                x => new EntityDescription {Name = x.Alias, FullName = x.FullName})
                .OrderBy(x => x.Name).ToArray();
        }

        public ResultsSummary Import(Type entity, Stream document)
        {
            var entityConfig = (from e in ImporterConfiguration.ImportableEntities
                                where e.Type.Equals(entity)
                                select e).First();
            // create layout
            var layout = LayoutFactory.Create(entity, new LayoutFactoryConfig(), ImporterConfiguration.ExcludedTypes);
            // validate layout
            var summary = LayoutValidator.Validate(layout, document);
            if (summary.HasErrors && (summary.ErrorSummary.HasErrors || summary.ErrorSummary.HasFatal))
            {
                return new ResultsSummary
                           {
                               Entity = entity.Name,
                               FechaReporte = DateTime.Now,
                               Validations = summary,
                               Success = false,
                               Operations = new OperationsSummary ()
                           };
            }
            // read data
            var data = FileReader.ReadFileData(layout, document);
            var totalCount = data.Data.Count;
            // validate data
            summary = DataValidator.Validate(data);
            if (summary.HasErrors && (summary.ErrorSummary.HasErrors || summary.ErrorSummary.HasFatal))
            {
                return new ResultsSummary
                           {
                               Entity = entity.Name,
                               FechaReporte = DateTime.Now,
                               Validations = summary,
                               Success = false,
                               Operations = new OperationsSummary(),
                               TotalRecords = totalCount,
                               IgnoredRecords = totalCount
                           };
            }
            // build entities
            var entities = BuildEntities(data);
            // validate references
            summary = ValidateReferences(entityConfig, layout, entities);
            if (summary.HasErrors && (summary.ErrorSummary.HasErrors || summary.ErrorSummary.HasFatal))
            {
                return new ResultsSummary
                {
                    Entity = entity.Name,
                    FechaReporte = DateTime.Now,
                    Validations = summary,
                    Success = false,
                    Operations = new OperationsSummary(),
                    TotalRecords = totalCount,
                    IgnoredRecords = totalCount
                };
            }
            // validate unique keys
            summary = ValidateUniqueKeys(entityConfig, layout, entities);
            if (summary.HasErrors && (summary.ErrorSummary.HasErrors || summary.ErrorSummary.HasFatal))
            {
                return new ResultsSummary
                {
                    Entity = entity.Name,
                    FechaReporte = DateTime.Now,
                    Validations = summary,
                    Success = false,
                    Operations = new OperationsSummary(),
                    TotalRecords = totalCount,
                    IgnoredRecords = totalCount
                };
            }
            // persist entities
            var ops = PersistEntities(entityConfig, layout, entities);
            return new ResultsSummary
                       {
                           Entity = entity.Name,
                           FechaReporte = DateTime.Now,
                           Validations = summary,
                           Operations = ops,
                           Success = true,
                           TotalRecords = data.Data.Count(),
                           ProcessedRecords = data.Data.Count()
                       };
        }

        public void GetLayout(Type entity)
        {
            using (var ms = new MemoryStream())
            {
                var layout = LayoutFactory.Create(entity, new LayoutFactoryConfig(), ImporterConfiguration.ExcludedTypes);
                FileBuilder.BuildFile(layout, ms);
                HttpContext.Current.Response.ContentType =
                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
                ms.WriteTo(HttpContext.Current.Response.OutputStream);
            }
        }

        #endregion

        private object BuildEntities(LayoutData data)
        {
            if (!_entityBuilders.ContainsKey(data.Layout.Entity.FullName))
            {
                var type = typeof (DefaultEntityBuilder<>).MakeGenericType(data.Layout.Entity);
                _entityBuilders.Add(data.Layout.Entity.FullName, Activator.CreateInstance(type));
                _entityBuilderMethods.Add(data.Layout.Entity.FullName,
                                          type.GetMethod("BuildEntities", new[] {typeof (LayoutData)}));
            }
            var builder = _entityBuilders[data.Layout.Entity.FullName];
            var method = _entityBuilderMethods[data.Layout.Entity.FullName];
            return method.Invoke(builder, new object[] {data});
        }

        private ValidationSummary ValidateReferences(EntityConfiguration entityConfig, EntityLayout layout, object entities)
        {
            if (entityConfig.HasValidator)
            {
                if (entityConfig.ReplaceDefaultValidator)
                {
                    var customvalidator = (IReferenceValidator)IoCFactory.Resolve(entityConfig.Validator);
                    var method = customvalidator.GetType().GetMethod("Validate").MakeGenericMethod(layout.Entity);
                    return (ValidationSummary)method.Invoke(customvalidator, new[] { layout, entities });
                }
                else
                {
                    var method = ReferenceValidator.GetType().GetMethod("Validate").MakeGenericMethod(layout.Entity);
                    var summary = (ValidationSummary)method.Invoke(ReferenceValidator, new[] { layout, entities });

                    var customValidator = (IReferenceValidator)IoCFactory.Resolve(entityConfig.Validator);
                    var customValidatorMethod = customValidator.GetType().GetMethod("Validate").MakeGenericMethod(layout.Entity);
                    var customValidatorSummary = (ValidationSummary)customValidatorMethod.Invoke(customValidator, new[] { layout, entities });

                    if (summary.HasErrors && customValidatorSummary.HasErrors)
                    {
                        summary.ErrorSummary.Errors = summary.ErrorSummary.Errors.Concat(customValidatorSummary.ErrorSummary.Errors).ToList();
                    }
                    if (!summary.HasErrors && customValidatorSummary.HasErrors)
                    {
                        summary.ErrorSummary.Errors = customValidatorSummary.ErrorSummary.Errors;
                    }
                    return summary;
                }
            }
            var defaultMethod = ReferenceValidator.GetType().GetMethod("Validate").MakeGenericMethod(layout.Entity);
            return (ValidationSummary)defaultMethod.Invoke(ReferenceValidator, new[] { layout, entities });
        }

        private ValidationSummary ValidateUniqueKeys(EntityConfiguration entityConfig, EntityLayout layout, object entities)
        {
            if (entityConfig.HasValidator)
            {
                return new ValidationSummary();
            }
            if (!_ukMethods.ContainsKey(layout.Entity.FullName))
            {
                _ukMethods.Add(layout.Entity.FullName,
                               UniqueKeyValidator.GetType().GetMethod("Validate").MakeGenericMethod(layout.Entity));
            }
            var method = _ukMethods[layout.Entity.FullName];
            return (ValidationSummary) method.Invoke(UniqueKeyValidator, new[] {layout, entities});
        }

        private OperationsSummary PersistEntities(EntityConfiguration entityConfig, EntityLayout layout, object entities)
        {
            var persister = entityConfig.HasPersister ?
                (IEntityPersister)Activator.CreateInstance(entityConfig.Perister) : Persister;
            if (!_persisterMethods.ContainsKey(layout.Entity.FullName))
            {
                _persisterMethods.Add(layout.Entity.FullName,
                                      persister.GetType().GetMethod("Persist").MakeGenericMethod(layout.Entity));
            }

            var method = _persisterMethods[layout.Entity.FullName];
            return (OperationsSummary)method.Invoke(persister, new[] { layout, entities });
        }
    }
}